
#include <sys/capability.h>
#include <sys/stat.h>
#include <libcapsicum.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>


void test(int lineNumber, int basefd, const char *name, int isCap,
          int readError, int writeError);

void checkResult(const char *call, const char *name, int result, int expectedError,
                 int firstLine, int secondLine);

void printCapability(int fd);

#define TEST(a, b, c, d, e) test(__LINE__, a, b, c, d, e);


int main(int argc, char *argv[])
{
	if(argc < 2)
		errx(-1, "Usage: test [mountpoint of 1M test FS]");

	const char *mountname = argv[1];
	int mountpoint = open(mountname, O_RDONLY);
	printf("mountpoint '%s' (FD %i)\n", mountname, mountpoint);



	/* bases for *at calls */
	int root	= openat(mountpoint, "/", O_RDONLY);
	if(root < 0)	err(-1, "Error opening /");

	int foo   	= openat(mountpoint, "foo", O_RDONLY);
	if(foo < 0) 	err(-1, "Error opening %s/foo", mountname);

	int bar   	= openat(mountpoint, "bar", O_RDONLY);
	if(bar < 0) 	err(-1, "Error opening %s/bar", mountname);


	const cap_rights_t DIR_RIGHTS
		= CAP_FSTAT | CAP_FCHMOD | CAP_READ | CAP_LOOKUP | CAP_ATBASE;

	int rootcap	= cap_new(root, DIR_RIGHTS);
	if(rootcap < 0)	err(-1, "Error in cap_new(root, DIR_RIGHTS)");

	int foocap	= cap_new(foo, DIR_RIGHTS);
	if(foocap < 0)	err(-1, "Error in cap_new(foo, DIR_RIGHTS)");

	int barcap	= cap_new(bar, DIR_RIGHTS);
	if(barcap < 0)	err(-1, "Error in cap_new(bar, DIR_RIGHTS)");

	int rootabs	= cap_new(root, DIR_RIGHTS | CAP_ABSOLUTEPATH);
	if(rootabs < 0)	err(-1, "Error in cap_new(root, DIR_RIGHTS | CAP_ABS...)");

	int fooabs	= cap_new(foo,  DIR_RIGHTS | CAP_ABSOLUTEPATH);
	if(fooabs < 0)	err(-1, "Error in cap_new(foo, DIR_RIGHTS | CAP_ABS...)");

	int barabs	= cap_new(bar,  DIR_RIGHTS | CAP_ABSOLUTEPATH);
	if(barabs < 0)	err(-1, "Error in cap_new(bar, DIR_RIGHTS | CAP_ABS...)");



	printf("testing non-capability mode... "); fflush(stdout);

	/* tests on the root directory */
	TEST(root,    "/",                           0, 0,            EPERM);
	TEST(rootcap, "/",                           1, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(rootabs, "/",                           1, 0,            EPERM);

	/* tests on the AT_FDCWD pseudo-descriptor */
	TEST(AT_FDCWD, ".",                          0, 0,            0);
	TEST(AT_FDCWD, "..",                         0, 0,            0);
	TEST(AT_FDCWD, "/",                          0, 0,            EPERM);
	TEST(AT_FDCWD, "/etc",                       0, 0,            EPERM);

	/* tests on the foo and bar directories */
	int dirs[2] = { foo, bar };
	int caps[2] = { foocap, barcap };
	int abs[2]  = { fooabs, barabs };

	for(int i = 0; i < 2; i++)
	{
		/* regular file descriptors */
		TEST(dirs[i], "",                    0, ENOENT,       ENOENT);
		TEST(dirs[i], ".",                   0, 0,            0);
		TEST(dirs[i], "/",                   0, 0,            EPERM);
		TEST(dirs[i], "fubar",               0, 0,            0);
		TEST(dirs[i], "../foo",              0, 0,            0);
		TEST(dirs[i], "../bar",              0, 0,            0);
		TEST(dirs[i], "../foo/foo1",         0, 0,            0);
		TEST(dirs[i], "../bar/bar1",         0, 0,            0);

		/* capabilities */
		TEST(caps[i], "",                    1, ENOENT,       ENOENT);
		TEST(caps[i], ".",                   1, 0,            0);
		TEST(caps[i], "/",                   1, ENOTCAPABLE,  ENOTCAPABLE);
		TEST(caps[i], "fubar",               1, 0,            0);
		TEST(caps[i], "../foo",              1, ENOTCAPABLE,  ENOTCAPABLE);
		TEST(caps[i], "../bar",              1, ENOTCAPABLE,  ENOTCAPABLE);
		TEST(caps[i], "../foo/foo1",         1, ENOTCAPABLE,  ENOTCAPABLE);
		TEST(caps[i], "../bar/bar1",         1, ENOTCAPABLE,  ENOTCAPABLE);

		/* capabilities with CAP_ABSOLUTEPATH */
		TEST( abs[i], "",                    1, ENOENT,       ENOENT);
		TEST( abs[i], ".",                   1, 0,            0);
		TEST( abs[i], "/",                   1, ENOTCAPABLE,  ENOTCAPABLE);
		TEST( abs[i], "fubar",               1, 0,            0);
		TEST( abs[i], "../foo",              1, ENOTCAPABLE,  ENOTCAPABLE);
		TEST( abs[i], "../bar",              1, ENOTCAPABLE,  ENOTCAPABLE);
		TEST( abs[i], "../foo/foo1",         1, ENOTCAPABLE,  ENOTCAPABLE);
		TEST( abs[i], "../bar/bar1",         1, ENOTCAPABLE,  ENOTCAPABLE);
	}
	printf("done.\n");


	/* enter capability mode */
	cap_enter();
	printf("testing capability mode... "); fflush(stdout);


	/* open(2) should definitely fail */
	int open_fd = open("/", O_RDONLY);
	checkResult("open", "/", open_fd, EOPNOTSUPP, __LINE__, __LINE__);


	/* tests on the root directory */
	TEST(root,    "/",                           0, 0,            EPERM);
	TEST(rootcap, "/",                           1, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(rootabs, "/",                           1, 0,            EPERM);


	/* tests on the AT_FDCWD pseudo-descriptor */
	TEST(AT_FDCWD, ".",                          0, EOPNOTSUPP,   EOPNOTSUPP);
	TEST(AT_FDCWD, "..",                         0, EOPNOTSUPP,   EOPNOTSUPP);
	TEST(AT_FDCWD, "/",                          0, EOPNOTSUPP,   EOPNOTSUPP);
	TEST(AT_FDCWD, "/etc",                       0, EOPNOTSUPP,   EOPNOTSUPP);


	/* foo, bar - these things ought to work */
	TEST(foo,    ".",                            0, 0,            0);
	TEST(foo,    "foo1",                         0, 0,            0);
	TEST(foo,    "foo1/foo1a",                   0, 0,            0);
	TEST(foo,    "foo1/foo1b",                   0, 0,            0);
	TEST(foo,    "foo2",                         0, 0,            0);
	TEST(foocap, ".",                            0, 0,            0);
	TEST(foocap, "foo1",                         0, 0,            0);
	TEST(foocap, "foo1/foo1a",                   0, 0,            0);
	TEST(foocap, "foo1/foo1b",                   0, 0,            0);
	TEST(foocap, "foo2",                         0, 0,            0);
	TEST(fooabs, ".",                            0, 0,            0);
	TEST(fooabs, "foo1",                         0, 0,            0);
	TEST(fooabs, "foo1/foo1a",                   0, 0,            0);
	TEST(fooabs, "foo1/foo1b",                   0, 0,            0);
	TEST(fooabs, "foo2",                         0, 0,            0);

	TEST(bar, ".",                               0, 0,            0);
	TEST(bar, "bar1",                            0, 0,            0);
	TEST(bar, "bar2",                            0, 0,            0);
	TEST(barcap, ".",                            0, 0,            0);
	TEST(barcap, "bar1",                         0, 0,            0);
	TEST(barcap, "bar2",                         0, 0,            0);
	TEST(barabs, ".",                            0, 0,            0);
	TEST(barabs, "bar1",                         0, 0,            0);
	TEST(barabs, "bar2",                         0, 0,            0);


	/* foo, bar - these things ought not work */
	TEST(foo, "",                                0, ENOENT,       ENOENT);
	TEST(foo, "/",                               0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(foo, "../",                             0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(foo, "../foo/foo1",                     0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(foo, "foo1/../../foo/foo1",             0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(foo, "../bar",                          0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(foo, "../bar/bar1",                     0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(foocap, "",                             0, ENOENT,       ENOENT);
	TEST(foocap, "/",                            0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(foocap, "../",                          0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(foocap, "../foo/foo1",                  0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(foocap, "foo1/../../foo/foo1",          0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(foocap, "../bar",                       0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(foocap, "../bar/bar1",                  0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(fooabs, "",                             0, ENOENT,       ENOENT);
	TEST(fooabs, "/",                            0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(fooabs, "../",                          0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(fooabs, "../foo/foo1",                  0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(fooabs, "foo1/../../foo/foo1",          0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(fooabs, "../bar",                       0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(fooabs, "../bar/bar1",                  0, ENOTCAPABLE,  ENOTCAPABLE);

	TEST(bar, "",                                0, ENOENT,       ENOENT);
	TEST(bar, "/",                               0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(bar, "../",                             0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(bar, "../bar/bar1",                     0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(bar, "../foo",                          0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(bar, "../foo/foo1",                     0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(bar, "../foo/foo1/foo1a",               0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(barcap, "",                             0, ENOENT,       ENOENT);
	TEST(barcap, "/",                            0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(barcap, "../",                          0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(barcap, "../bar/bar1",                  0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(barcap, "../foo",                       0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(barcap, "../foo/foo1",                  0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(barcap, "../foo/foo1/foo1a",            0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(barabs, "",                             0, ENOENT,       ENOENT);
	TEST(barabs, "/",                            0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(barabs, "../",                          0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(barabs, "../bar/bar1",                  0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(barabs, "../foo",                       0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(barabs, "../foo/foo1",                  0, ENOTCAPABLE,  ENOTCAPABLE);
	TEST(barabs, "../foo/foo1/foo1a",            0, ENOTCAPABLE,  ENOTCAPABLE);

	printf("done.\n");

	printf("All done.\n");

	return 0;
}

void test(int lineNumber, int basefd, const char *name, int isCap,
          int readError, int writeError)
{
	cap_rights_t openatrights = -1;
	if(isCap && cap_getrights(basefd, &openatrights))
		warn("%s:%i\tError in cap_getrights(basefd)", __FILE__, __LINE__);

	// try faccessat(2)
	int result = faccessat(basefd, name, F_OK, 0);
	checkResult("faccessat", name, result, readError, __LINE__, lineNumber);

	// try fstatat(2)
	struct stat s;
	result = fstatat(basefd, name, &s, 0);
	checkResult("fstatat", name, result, readError, __LINE__, lineNumber);

	// try fchmodat(2)
	result = fchmodat(basefd, name, 0777, 0);
	checkResult("fchmodat", name, result, writeError, __LINE__, lineNumber);

	// try openat(2)
	int fd = openat(basefd, name, O_RDONLY);
	checkResult("openat", name, fd, readError, __LINE__, lineNumber);
	
	if((fd >= 0) && (readError == 0) && (openatrights != -1))
	{
		cap_rights_t fdrights;
		if(isCap && cap_getrights(fd, &fdrights))
			warn("%s:%i\tcap_getrights(fd) failed", __FILE__, __LINE__);

		else if(fdrights != openatrights)
			warnx("The rights of the new capability differ"
			      " from those of the openat() base");
	}

	fflush(stderr);
	close(fd);
}


void checkResult(const char *call, const char *name, int result, int expectedError,
                 int firstLine, int secondLine)
{
	const char *filename = __FILE__;

	if(result >= 0)
	{
		if(expectedError != 0)
			warnx("%s('%s') succeeded; expected error %i\n"
			      "\tat %s:%i\n\tat %s:%i",
			      call, name, expectedError,
			      filename, firstLine, filename, secondLine);
	}
	else
	{
		if(expectedError == 0)
			warn("Error in %s('%s')\n"
			     "\tat %s:%i\n\tat %s:%i",
			     call, name,
			     filename, firstLine, filename, secondLine);

		else if(errno != expectedError)
			warn("Error in %s('%s') != %i\n"
			      "\tat %s:%i\n\tat %s:%i",
			     call, name, expectedError,
			     filename, firstLine, filename, secondLine);
	}
}


void printCapability(int fd)
{
	cap_rights_t rights;
	if(cap_getrights(fd, &rights))
		warn("Error in cap_getrights(fd)");

	printf("FD %i: %016x", fd, (unsigned int) rights);

	if(!rights)
	{
		printf("\n");
		return;
	}
	else printf(": ");

	if(rights & CAP_READ)		printf("CAP_READ ");
	if(rights & CAP_WRITE)		printf("CAP_WRITE ");
	if(rights & CAP_SEEK)		printf("CAP_SEEK ");
	if(rights & CAP_GETPEERNAME)	printf("CAP_GETPEERNAME ");
	if(rights & CAP_GETSOCKNAME)	printf("CAP_GETSOCKNAME ");
	if(rights & CAP_FCHFLAGS)	printf("CAP_FCHFLAGS ");
	if(rights & CAP_IOCTL)		printf("CAP_IOCTL ");
	if(rights & CAP_FSTAT)		printf("CAP_FSTAT ");
	if(rights & CAP_MMAP)		printf("CAP_MMAP ");
	if(rights & CAP_FCNTL)		printf("CAP_FCNTL ");
	if(rights & CAP_EVENT)		printf("CAP_EVENT ");
	if(rights & CAP_FSYNC)		printf("CAP_FSYNC ");
	if(rights & CAP_FCHOWN)		printf("CAP_FCHOWN ");
	if(rights & CAP_FCHMOD)		printf("CAP_FCHMOD ");
	if(rights & CAP_FTRUNCATE)	printf("CAP_FTRUNCATE ");
	if(rights & CAP_FLOCK)		printf("CAP_FLOCK ");
	if(rights & CAP_FSTATFS)	printf("CAP_FSTATFS ");
	if(rights & CAP_REVOKE)		printf("CAP_REVOKE ");
	if(rights & CAP_FEXECVE)	printf("CAP_FEXECVE ");
	if(rights & CAP_FPATHCONF)	printf("CAP_FPATHCONF ");
	if(rights & CAP_FUTIMES)	printf("CAP_FUTIMES ");
	if(rights & CAP_ACL_GET)	printf("CAP_ACL_GET ");
	if(rights & CAP_ACL_SET)	printf("CAP_ACL_SET ");
	if(rights & CAP_ACL_DELETE)	printf("CAP_ACL_DELETE ");
	if(rights & CAP_ACL_CHECK)	printf("CAP_ACL_CHECK ");
	if(rights & CAP_EXTATTR_GET)	printf("CAP_EXTATTR_GET ");
	if(rights & CAP_EXTATTR_SET)	printf("CAP_EXTATTR_SET ");
	if(rights & CAP_EXTATTR_DELETE)	printf("CAP_EXTATTR_DELETE ");
	if(rights & CAP_EXTATTR_LIST)	printf("CAP_EXTATTR_LIST ");
	if(rights & CAP_MAC_GET)	printf("CAP_MAC_GET ");
	if(rights & CAP_MAC_SET)	printf("CAP_MAC_SET ");
	if(rights & CAP_ACCEPT)		printf("CAP_ACCEPT ");
	if(rights & CAP_CONNECT)	printf("CAP_CONNECT ");
	if(rights & CAP_BIND)		printf("CAP_BIND ");
	if(rights & CAP_GETSOCKOPT)	printf("CAP_GETSOCKOPT ");
	if(rights & CAP_SETSOCKOPT)	printf("CAP_SETSOCKOPT ");
	if(rights & CAP_LISTEN)		printf("CAP_LISTEN ");
	if(rights & CAP_SHUTDOWN)	printf("CAP_SHUTDOWN ");
	if(rights & CAP_PEELOFF)	printf("CAP_PEELOFF ");
	if(rights & CAP_LOOKUP)		printf("CAP_LOOKUP ");
	if(rights & CAP_SEM_POST)	printf("CAP_SEM_POST ");
	if(rights & CAP_SEM_WAIT)	printf("CAP_SEM_WAIT ");
	if(rights & CAP_SEM_GETVALUE)	printf("CAP_SEM_GETVALUE ");
	if(rights & CAP_KEVENT)		printf("CAP_KEVENT ");
	if(rights & CAP_PDGETPID)	printf("CAP_PDGETPID ");
	if(rights & CAP_PDWAIT)		printf("CAP_PDWAIT ");
	if(rights & CAP_PDKILL)		printf("CAP_PDKILL ");
	if(rights & CAP_MAPEXEC)	printf("CAP_MAPEXEC ");
	if(rights & CAP_TTYHOOK)	printf("CAP_TTYHOOK ");
	if(rights & CAP_FCHDIR)		printf("CAP_FCHDIR ");
	if(rights & CAP_FSCK)		printf("CAP_FSCK ");
	if(rights & CAP_ATBASE)		printf("CAP_ATBASE ");
	if(rights & CAP_ABSOLUTEPATH)	printf("CAP_ABSOLUTEPATH ");
	if(rights & CAP_MASK_VALID)	printf("CAP_MASK_VALID ");

	printf("\n");
}

